﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Konves.Scripture.Data;

namespace Konves.Scripture
{
    /// <summary>
    /// Represents a collection of one or more ranges of scripture verses.
    /// </summary>
    public class Reference : List<Range>
    {
        /// <summary>
        /// Converts the string representation of a scripture reference to its <see cref="Reference"/> equivalent.
        /// </summary>
        /// <param name="reference">A string containing a scripture reference to convert.</param>
        /// <returns>
        /// A <see cref="Reference"/> equivalent to the scripture reference contained in <paramref name="reference"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        ///     <paramref name="reference"/> is null.</exception>
        /// <exception cref="System.ArgumentException">
        ///     <paramref name="reference"/> does not contain any text.</exception>
        public static Reference Parse(string reference)
        {
            return Parse(reference, RepositoryMode.Safe);
        }

        public static bool TryParse(string input, out Reference reference)
        {
            return TryParse(input, RepositoryMode.Safe, out reference);
        }

        public static bool TryParse(string input, RepositoryMode mode, out Reference reference)
        {
            if (input == null)
            {
                reference = null;
                return false;
            }

            if (string.IsNullOrWhiteSpace(input))
            {
                reference = null;
                return false;
            }
            
            reference = new Reference();

            RawReference rawRef = RawReference.Parse(input);

            foreach (RawRange range in rawRef)
            {
                var x = Range.Create(range, mode);

                if (x == null)
                {
                    if (mode == RepositoryMode.Strict)
                    {
                        reference = null;
                        return false;
                    }
                }                    
                else
                    reference.Add(x);
            }

            return true;
        }

        /// <summary>
        /// Converts the string representation of a scripture reference to its <see cref="Reference"/> equivalent.
        /// </summary>
        /// <param name="input">A string containing a scripture reference to convert.</param>
        /// <param name="mode">The mode.</param>
        /// <returns>
        /// A <see cref="Reference"/> equivalent to the scripture reference contained in <paramref name="input"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        ///   <paramref name="input"/> is null.</exception>
        ///   
        /// <exception cref="System.ArgumentException">
        ///   <paramref name="input"/> does not contain any text.</exception>
        public static Reference Parse(string input, RepositoryMode mode)
        {
            if (input == null)
                throw new ArgumentNullException("input", "reference is null");

            if (string.IsNullOrWhiteSpace(input))
                throw new ArgumentException("input does not contain any text", "input");
            
            Reference result = new Reference();

            if (TryParse(input, mode, out result))
                return result;
            else
                throw new ReferenceFormatException();


            //RawReference rawRef = RawReference.Parse(reference);

            //foreach (RawRange range in rawRef)
            //{
            //    var x = RangeInfo.Create(range, mode);

            //    if (x != null)
            //        result.Add(x);
            //    else
            //        if (mode == RepositoryMode.Strict)
            //            throw new ReferenceFormatException();
            //}

            //return result;
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            List<string> rangeStrings = new List<string>();

            string result = string.Empty;

            int currentBook = -1;
            int currentChapter = -1;
            int currentVerse = -1;

            foreach (Range range in this)
            {
                if (range.Start.BookId == currentBook && range.Start.ChapterNumber == currentChapter)
                {
                    if (range.End.BookId == range.Start.BookId && range.End.ChapterNumber == range.Start.ChapterNumber && range.End.VerseNumber == range.Start.VerseNumber)
                    {
                        // See test 2
                        result = range.End.VerseNumber.ToString();
                    }
                    else if (range.End.BookId == range.Start.BookId && range.End.ChapterNumber == range.Start.ChapterNumber)
                    {
                        // See test 3
                        result = range.Start.VerseNumber.ToString() + "-" + range.End.VerseNumber.ToString();
                    }
                    else if (range.End.BookId == range.Start.BookId)
                    {
                        // See test 6
                        result = range.Start.VerseNumber.ToString() + "-" + range.End.ChapterNumber.ToString() + ":" + range.End.VerseNumber.ToString();
                    }
                    else
                    {
                        // See test 7
                        result = range.Start.VerseNumber.ToString() + " - " + range.End.ToString();
                    }
                }
                else if (range.Start.BookId == currentBook)
                {
                    if (range.End.BookId == range.Start.BookId && range.End.ChapterNumber == range.Start.ChapterNumber && range.End.VerseNumber == range.Start.VerseNumber)
                    {
                        // See test 4
                        result = range.Start.ChapterNumber.ToString() + ":" + range.Start.VerseNumber.ToString();                    
                    }
                    else if (range.End.BookId == range.Start.BookId && range.End.ChapterNumber == range.Start.ChapterNumber)
                    {
                        // See test 5
                        result = range.Start.ChapterNumber.ToString() + ":" + range.Start.VerseNumber.ToString() + "-" + range.End.VerseNumber.ToString();
                    }
                    else if (range.End.BookId == range.Start.BookId)
                    {
                        // See test 8
                        result = range.Start.ChapterNumber.ToString() + ":" + range.Start.VerseNumber.ToString() + "-" + range.End.ChapterNumber.ToString() + ":" + range.End.VerseNumber.ToString();
                    }
                    else
                    {
                        // See test 9
                        result = range.Start.ChapterNumber.ToString() + ":" + range.Start.VerseNumber.ToString() + " - " + range.End.ToString();
                    }
                }
                else
                {
                    // See test 1
                    result = range.ToString();
                }

                if (currentBook != -1)
                {
                    if (range.Start.BookId != currentBook)
                        result = "; " + result;
                    else
                        result = "," + result;
                }

                rangeStrings.Add(result);

                currentBook = range.End.BookId;
                currentChapter = range.End.ChapterNumber;
                currentVerse = range.End.VerseNumber;
            }
            
            return string.Join("", rangeStrings);

        }

        /// <summary>
        /// Determines whether this <see cref="Reference"/> contains the specified <see cref="Verse"/>.
        /// </summary>
        /// <param name="verse">The <see cref="Verse"/>.</param>
        /// <returns>
        ///   <c>true</c> if the <see cref="Reference"/> contains the specified <see cref="Verse"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="verse"/> is null.</exception>
        public bool Contains(Verse verse)
        {
            if (verse == null)
                throw new ArgumentNullException("verse", "verse is null");

            return this.Any(range => range.Contains(verse));
        }

        /// <summary>
        /// Determines whether this <see cref="Reference"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>.
        /// </summary>
        /// <param name="range">The <see cref="Range"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Reference"/> contains all of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="range"/> is null.</exception>
        new public bool Contains(Range range)
        {
            if (range == null)
                throw new ArgumentNullException("range", "range is null");
            
            return this.Any(r => r.Contains(range));
        }

        /// <summary>
        /// Determines whether this <see cref="Reference"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>.
        /// </summary>
        /// <param name="range">The <see cref="Range"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Reference"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Range"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="range"/> is null.</exception>
        public bool Intersects(Range range)
        {
            if (range == null)
                throw new ArgumentNullException("range", "range is null");

            return this.Any(r => r.Intersects(range));
        }

        /// <summary>
        /// Determines whether this <see cref="Reference"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>.
        /// </summary>
        /// <param name="reference">The <see cref="Reference"/>.</param>
        /// <returns>
        ///   <c>true</c> if this <see cref="Reference"/> contains any of the <see cref="Verse">Verses</see>
        /// in the specified <see cref="Reference"/>; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="reference"/> is null.</exception>
        public bool Intersects(Reference reference)
        {
            if (reference == null)
                throw new ArgumentNullException("reference", "reference is null");

            foreach (Range range in this)
                if (range.Intersects(reference))
                    return true;
            return false;
        }

        internal class EqualityComparer : IEqualityComparer<Reference>
        {
            public bool Equals(Reference x, Reference y)
            {
                return this.GetHashCode(x) == this.GetHashCode(y);
            }

            public int GetHashCode(Reference obj)
            {
                int result = 0;

                foreach (Range range in obj)
                {
                    result ^= range.GetHashCode();
                }
                return result;
            }
        }
    }
}
